// MyPNG.cpp: implementation of the CMyPNG class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "MyPNG.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CMyPNG::CMyPNG()
{
	info_ptr = NULL;
	end_info_ptr = NULL;
	width = height = 0;
	bBPP = 0;
	pImg = NULL;
}

CMyPNG::~CMyPNG()
{
	//if (info_ptr) delete info_ptr;
	//if (end_info_ptr) delete end_info_ptr;
	if (pImg) delete pImg;
}

bool CMyPNG::bReadPNG(LPCTSTR lpFilename)
{
	if (!isPNG(lpFilename)) return false;

	png_structp png_ptr;
	png_infop info_ptr;
	unsigned int sig_read = 0;
	//png_uint_32 width, height;
	int bit_depth, color_type, interlace_type;
	FILE *fp;

	if ((_tfopen_s(&fp, lpFilename, _T("rb"))) || !fp) {
		return false;
	}

	/* Create and initialize the png_struct with the desired error handler
	* functions.  If you want to use the default stderr and longjump method,
	* you can supply NULL for the last three parameters.  We also supply the
	* the compiler header file version, so that we know if the application
	* was compiled with a compatible version of the library.  REQUIRED
	*/
	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
		NULL, NULL, NULL);

	if (png_ptr == NULL) {
		fclose(fp);
		return false;
	}

	/* Allocate/initialize the memory for image information.  REQUIRED. */
	info_ptr = png_create_info_struct(png_ptr);
	if (info_ptr == NULL) {
		fclose(fp);
		png_destroy_read_struct(&png_ptr, NULL, NULL);
		return false;
	}

	/* Set error handling if you are using the setjmp/longjmp method (this is
	* the normal method of doing things with libpng).  REQUIRED unless you
	* set up your own error handlers in the png_create_read_struct() earlier.
	*/

	if (setjmp(png_jmpbuf(png_ptr))) {
		/* Free all of the memory associated with the png_ptr and info_ptr */
		png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
		fclose(fp);
		/* If we get here, we had a problem reading the file */
		return false;
	}


	png_init_io(png_ptr, fp);
	png_set_sig_bytes(png_ptr, sig_read);
	png_read_info(png_ptr, info_ptr);

	png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
		&interlace_type, NULL, NULL);

	png_set_strip_16(png_ptr);

	/* Expand paletted colors into true RGB triplets */
	if (color_type == PNG_COLOR_TYPE_PALETTE)
		png_set_palette_to_rgb(png_ptr);

	/* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */
	if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
		png_set_expand_gray_1_2_4_to_8(png_ptr);

	/* Expand paletted or RGB images with transparency to full alpha channels
	* so the data will be available as RGBA quartets.
	*/
	if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
		png_set_tRNS_to_alpha(png_ptr);

	int number_passes = png_set_interlace_handling(png_ptr);

	//png_set_alpha_mode(png_ptr, PNG_ALPHA_PNG, 1.0);

	/* Optional call to gamma correct and add the background to the palette
	* and update info structure.  REQUIRED if you are expecting libpng to
	* update the palette for you (ie you selected such a transform above).
	*/
	png_read_update_info(png_ptr, info_ptr);

	/* Allocate the memory to hold the image using the fields of info_ptr. */
	if (pImg) delete pImg;
	pImg = new BYTE[width*height*4];
	BYTE *pImg_;

	//BYTE channels = png_ptr->channels;
	//if (channels<3) channels=3;
	//bBPP = bit_depth*channels;
	bBPP = (png_ptr->info_rowbytes)/width*8;

	for (int pass = 0; pass < number_passes; pass++) {
		for (png_uint_32 y = 0; y < height; y++) {
			//pImg_ = pImg+y*width*channels;
			pImg_ = pImg+y*(png_ptr->info_rowbytes);
			png_read_rows(png_ptr, (png_bytepp)(&pImg_), NULL, 1);
		}
	}

	/* Read rest of file, and get additional chunks in info_ptr - REQUIRED */
	png_read_end(png_ptr, info_ptr);

	/* Clean up after the read, and free any memory allocated - REQUIRED */
	png_destroy_read_struct(&png_ptr, &info_ptr, NULL);

	/* Close the file */
	fclose(fp);

	return true;
}

bool CMyPNG::bWritePNG(LPCTSTR lpFilename)
{
	if (!pImg || !width || !height) return false;

	FILE *fp;
	png_structp png_ptr;
	png_infop info_ptr;

	/* Open the file */
	if (_tfopen_s(&fp, lpFilename, _T("wb")) || !fp)
		return (false);

	/* Create and initialize the png_struct with the desired error handler
	* functions.  If you want to use the default stderr and longjump method,
	* you can supply NULL for the last three parameters.  We also check that
	* the library version is compatible with the one used at compile time,
	* in case we are using dynamically linked libraries.  REQUIRED.
	*/
	png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
		NULL, NULL, NULL);

	if (png_ptr == NULL) {
		fclose(fp);
		return (false);
	}

	/* Allocate/initialize the image information data.  REQUIRED */
	info_ptr = png_create_info_struct(png_ptr);
	if (info_ptr == NULL) {
		fclose(fp);
		png_destroy_write_struct(&png_ptr,  NULL);
		return (false);
	}

	/* Set error handling.  REQUIRED if you aren't supplying your own
	* error handling functions in the png_create_write_struct() call.
	*/
	if (setjmp(png_jmpbuf(png_ptr))) {
		/* If we get here, we had a problem writing the file */
		fclose(fp);
		png_destroy_write_struct(&png_ptr, &info_ptr);
		return (false);
	}

	/* Set up the output control if you are using standard C streams */
	png_init_io(png_ptr, fp);

	png_set_IHDR(png_ptr, info_ptr, width, height, 8,
		(bBPP == 32)?PNG_COLOR_TYPE_RGB_ALPHA:PNG_COLOR_MASK_COLOR,
		PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);

	/* Optional significant bit (sBIT) chunk */
	png_color_8 sig_bit;

	/* Otherwise, if we are dealing with a color image then */
	sig_bit.red = 8;
	sig_bit.green = 8;
	sig_bit.blue = 8;

	/* If the image has an alpha channel then */
	sig_bit.alpha = (bBPP == 32)?8:0;

	png_set_sBIT(png_ptr, info_ptr, &sig_bit);


	/* Write the file header information.  REQUIRED */
	png_write_info(png_ptr, info_ptr);

	//png_uint_32 height, width;
	BYTE *pImg_ = pImg;
	UINT widthinByte = width*(bBPP/8);

	for (png_uint_32 y = 0; y < height; y++) {
		png_write_rows(png_ptr, (png_bytepp)(&pImg_), 1);
		pImg_ += widthinByte;
	}

	png_write_end(png_ptr, info_ptr);

	/* Clean up after the write, and free any memory allocated */
	png_destroy_write_struct(&png_ptr, &info_ptr);

	/* Close the file */
	fclose(fp);

	/* That's it */
	return (true);
}

bool CMyPNG::isPNG(LPCTSTR lpFilename)
{
	if (!lpFilename) return false;
	FILE *f;
	if (_tfopen_s(&f, lpFilename, _T("rb")) || !f)
		return false;

	static BYTE header[8];
	if (fread(header, 1, 8, f) != 8) {
		fclose(f);
		return false;
	}

	fclose(f);
	return (header[0] == 0x89 && header[1] == 0x50 && 
		header[2] == 0x4e && header[3] == 0x47 && 
		header[4] == 0x0d && header[5] == 0x0a && 
		header[6] == 0x1a && header[7] == 0x0a);
}
